package cn.siriusbot.siriuspro.bot.plugin;

import cn.siriusbot.siriuspro.bot.BotApi;
import cn.siriusbot.siriuspro.bot.BotApiBuilder;
import cn.siriusbot.siriuspro.bot.api.ResourceApi;
import cn.siriusbot.siriuspro.bot.application.SiriusApplication;
import cn.siriusbot.siriuspro.bot.application.SiriusApplicationInfo;
import cn.siriusbot.siriuspro.bot.plugin.database.PlugInDatabaseInfo;
import cn.siriusbot.siriuspro.bot.plugin.factory.JavaSiriusAppFactory;
import cn.siriusbot.siriuspro.bot.plugin.factory.SiriusAppFactory;
import cn.siriusbot.siriuspro.bot.plugin.pojo.QuickAppDependence;
import cn.siriusbot.siriuspro.bot.plugin.pojo.QuickAppEntity;
import cn.siriusbot.siriuspro.bot.pojo.event.BotEventMessage;
import cn.siriusbot.siriuspro.config.Constant;
import cn.siriusbot.siriuspro.error.MsgException;
import cn.siriusbot.siriuspro.uitls.AppContextUtil;
import cn.siriusbot.siriuspro.web.R.R;
import cn.siriusbot.siriuspro.web.pojo.BotHttpRequest;
import cn.siriusbot.siriuspro.web.websocket.surface.WebsocketSession;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.log4j.Log4j2;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.codec.digest.DigestUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 快应用
 */
@Log4j2
public class QuickPlugInClient extends PlugInDatabaseInfo implements PlugInClient, ExpandClient {

    private final String uuid;
    private final SiriusApplicationInfo info;

    private final JavaPlugInClient javaPlugInClient;

    public SiriusAppFactory getFactory() {
        return javaPlugInClient.getFactory();
    }

    /**
     * HttpClient单例
     */
    public static final OkHttpClient httpClient = new OkHttpClient.Builder()
            .callTimeout(Duration.ofSeconds(10))     // 设置超时时间为10秒
            .build();

    /**
     * 推送ws连接事件
     *
     * @param session
     */
    @Override
    public void putWebSocketOpen(WebsocketSession session) {
        javaPlugInClient.putWebSocketOpen(session);
    }

    /**
     * 推送ws消息事件
     *
     * @param session
     * @param message
     */
    @Override
    public void putWebSocketMessage(WebsocketSession session, String message) {
        javaPlugInClient.putWebSocketMessage(session, message);
    }

    /**
     * 推送ws关闭事件
     *
     * @param session
     */
    @Override
    public void putWebSocketClose(WebsocketSession session) {
        javaPlugInClient.putWebSocketClose(session);
    }

    private static class QuickClassLoader extends ClassLoader {
        Map<String, Class<?>> classMap = new ConcurrentHashMap<>();

        QuickAppEntity entity;

        public QuickClassLoader(ClassLoader parent, QuickAppEntity entity) {
            super(parent);
            this.entity = entity;
        }

        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            // 判断是否为依赖列表的
            final Optional<QuickAppDependence> first = entity.getDependence().stream().filter(s -> s.getClassName().equals(name)).findFirst();
            if (first.isPresent()) {
                if (classMap.containsKey(name)) {
                    return classMap.get(name);
                }
                // 是依赖列表的
                final QuickAppDependence quickAppDependence = first.get();
                try {
                    final Class<?> clazz = loadLibClass(quickAppDependence.getClassName());
                    classMap.put(name, clazz);
                    return clazz;
                } catch (IOException e) {
                    e.printStackTrace();
                    return super.findClass(name);
                }
            }
            return super.findClass(name);
        }


        public Class<?> loadLibClass(String libName) throws IOException {
            // 查找是否已经添加过
            if (classMap.containsKey(libName)) {
                return classMap.get(libName);
            }
            // 从网络获取
            Request request = new Request.Builder()
                    .url(url + entity.getName() + "/" + libName)
                    .get()
                    .build();
            Response response = httpClient.newCall(request).execute();
            final byte[] bytes = Objects.requireNonNull(response.body()).bytes();
            final Class<?> clazz = defineClass(libName, bytes);
            classMap.put(libName, clazz);
            return clazz;
        }

        public Class<?> defineClass(String name, byte[] b) {
            // ClassLoader是个抽象类，而ClassLoader.defineClass 方法是protected的
            // 所以我们需要定义一个子类将这个方法暴露出来
            return super.defineClass(name, b, 0, b.length);
        }
    }

    private static final String url = Constant.FAST_APP_URL + "/api/quick/get-class/";
    String resourceUrl = Constant.FAST_APP_URL + "/api/quick/get-resource/";
    String libUrl = Constant.FAST_APP_URL + "/api/quick/get-import/";
    String packageName = "cn.siriusbot.siriusproserver.app";

    private byte[] getBytecode(String name) throws IOException {
        Request request = new Request.Builder()
                .url(url + name + "/main")
                .get()
                .build();
        Response response = httpClient.newCall(request).execute();
        return Objects.requireNonNull(response.body()).bytes();
    }


    private File InjectedResource(String name, String resourceName, ResourceApi resourceApi) throws IOException {
        File file = resourceApi.getFile(resourceName);
        String hash = null;
        if (file.exists()) {
            try (FileInputStream stream = new FileInputStream(file)) {
                hash = DigestUtils.sha256Hex(stream);
            } catch (Throwable ignored) {

            }
        }
        // 获取服务器资源
        Request request = new Request.Builder()
                .url(resourceUrl + name + "/" + resourceName + (hash == null ? "" : ("?hash=" + hash)))
                .get()
                .build();
        Response response = httpClient.newCall(request).execute();
        if (response.code() == 200) {// 正常接收
            Files.write(Path.of(file.getAbsolutePath()), Objects.requireNonNull(response.body()).bytes());
        }
//        System.out.println(response.code());
        return file;
    }

    /**
     * 构造函数
     * @param appName           快应用名
     * @param loadDependence    是否自动加载依赖
     */
    public QuickPlugInClient(String appName, boolean loadDependence) {
        this.uuid = UUID.randomUUID().toString();
        // 远程获取字节码
        try {
            // 获取依赖对象
            Request request = new Request.Builder()
                    .url(libUrl + appName)
                    .get()
                    .build();
            Response response = httpClient.newCall(request).execute();
            if (response.code() != 200) {
                throw new MsgException(500, "加载快应用错误!");
            }

            List<Class<?>> libsClazz = new ArrayList<>();
            String string = Objects.requireNonNull(response.body()).string();
            QuickAppEntity quickAppEntity = JSONObject.parseObject(string, QuickAppEntity.class);

            // 创建类加载器
            QuickClassLoader quickClassLoader = new QuickClassLoader(Thread.currentThread().getContextClassLoader(), quickAppEntity);
            quickAppEntity.getDependence().forEach(item -> {
                try {
                    final Class<?> aClass = quickClassLoader.loadLibClass(item.getClassName());
                    libsClazz.add(aClass);
                } catch (IOException e) {
                    e.printStackTrace();
                    throw new MsgException(500, String.format("加载库[%s]时错误!", item.getClassName()));
                }
            });

            BotApiBuilder botApiBuilder = AppContextUtil.getBean(BotApiBuilder.class);
            byte[] bytes = getBytecode(appName);

            Class<?> clazz = quickClassLoader.defineClass(packageName + "." + appName + ".AppMain", bytes);
            libsClazz.add(clazz);
            // 创建工厂类
            SiriusAppFactory factory = new JavaSiriusAppFactory();
            libsClazz.forEach(factory::scanComponent);
            // 注入常用工具
            SiriusApplication application = factory.getComponent(SiriusApplication.class);
            BotApi botApi = botApiBuilder.build(application.appInfo());

            factory.addBean("botApi", botApi);
            factory.addBean("resourceApi", botApi.resourceApi());
            factory.addBean("siriusUtils", botApi.siriusUtils());
            factory.addBean("configApi", botApi.configApi());
            factory.addBean("botManageApi", botApi.botManageApi());
            factory.addBean("announcesApi", botApi.announcesApi());
            factory.addBean("apiPermissionApi", botApi.apiPermissionApi());
            factory.addBean("audioApi", botApi.audioApi());
            factory.addBean("channelApi", botApi.channelApi());
            factory.addBean("channelPermissionsApi", botApi.channelPermissionsApi());
            factory.addBean("dmsApi", botApi.dmsApi());
            factory.addBean("forumApi", botApi.forumApi());
            factory.addBean("guildApi", botApi.guildApi());
            factory.addBean("messageApi", botApi.messageApi());
            factory.addBean("memberApi", botApi.memberApi());
            factory.addBean("messageReactionApi", botApi.messageReactionApi());
            factory.addBean("messageSettingApi", botApi.messageSettingApi());
            factory.addBean("noSpeakApi", botApi.noSpeakApi());
            factory.addBean("pinsMessageApi", botApi.pinsMessageApi());
            factory.addBean("roleApi", botApi.roleApi());
            factory.addBean("scheduleApi", botApi.scheduleApi());
            factory.addBean("userApi", botApi.userApi());

            // 添加远程资源类
            List<String> resources = quickAppEntity.getResources();
            if (resources != null) {

                resources.forEach(item -> {
                    try {
                        final File file = InjectedResource(quickAppEntity.getName(), item, botApi.resourceApi());
                        factory.addBean(item, file);
                    } catch (IOException e) {
                        log.error(String.format("加载资源%s失败!", item));
                    }
                });
            }

            // 初始化组件
            factory.startComponent(loadDependence);

            this.info = application.appInfo();
            super.updateInfo();
            application.SiriusAppInit(botApi);
            this.javaPlugInClient = new JavaPlugInClient(application, application.appInfo(), factory);

        } catch (MsgException e) {
            throw e;
        } catch (Throwable e) {
            log.error(String.format("获取快应用[%s]失败", appName), e);
            throw new MsgException(500, "加载快应用错误!");
        }
    }

    /**
     * 生成一个唯一的uuid作为键
     */
    @Override
    public String getUuid() {
        return uuid;
    }

    /**
     * 插件唯一id
     */
    @Override
    public String getPackageName() {
        return info.getPackageName();
    }

    /**
     * 插件详细
     */
    @Override
    public SiriusApplicationInfo getInfo() {
        return info;
    }

    /**
     * 推送事件
     *
     * @param botId   机器人id
     * @param message 事件对象
     */
    @Override
    public void putEvent(String botId, BotEventMessage message) {
        this.javaPlugInClient.putEvent(botId, message);
    }

    /**
     * 插件web请求处理
     *
     * @return R对象
     */
    @Override
    public R webPost(BotHttpRequest request) {
        return this.javaPlugInClient.webPost(request);
    }

    /**
     * 获取插件的BotApi
     *
     * @return BotApi对象
     */
    @Override
    public BotApi getBotApi() {
        return getFactory().getComponent(BotApi.class);
    }
}
